<?php
/**
 * Nuwani PHP IRC Bot Framework
 * Copyright (c) 2006-2010 The Nuwani Project
 *
 * Nuwani is a framework for IRC Bots built using PHP. Nuwani speeds up bot 
 * development by handling basic tasks as connection- and bot management, timers
 * and module managing. Features for your bot can easily be added by creating
 * your own modules, which will receive callbacks from the framework.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 * @copyright Copyright (c) 2006-2011 The Nuwani Project
 * @author Peter Beverloo <peter@lvp-media.com>
 * @author Dik Grapendaal <dik.grapendaal@gmail.com>
 * @see http://nuwani.googlecode.com
 */

require __DIR__ . '/UnoEngine.php';

use Nuwani \ ModuleManager;

/**
 * Uno Nuwani Module
 * 
 * @copyright Copyright (c) 2011 The Nuwani Project, http://nuwani.googlecode.com/
 * @author Dik Grapendaal <dik.grapendaal@gmail.com>
 * @version $Id: Module.php 148 2011-07-26 19:10:23Z dik.grapendaal $
 * @package Modules
 */

class Uno extends ModuleBase
{
        
        /**
         * The data directory to store our stuff in.
         */
        
        const   DATA_DIR        = 'Data/Uno';
        
        /**
         * Channels where Uno is enabled in.
         * 
         * @var array
         */
        
        private $mChannels;
        
        /**
         * The object where the engine sends its output to. The module keeps
         * this updated with the Bot object, the current channel and network.
         * 
         * @var IrcOutput
         */
        
        private $mOutput;
        
        /**
         * The object where the engine gets its input from. The module sends all
         * messages from the channel where Uno is currently active to this
         * object.
         * 
         * @var IrcInput
         */
        
        private $mInput;
        
        /**
         * The Uno game engine. This is where all the logic happens.
         * 
         * @var UnoEngine
         */
        
        private $mEngine;
        
        /**
         * The constructor prepares the things we need before moving on.
         */
        
        public function __construct ()
        {
                // Prepare our own directory to work with.
                if (!is_dir (self :: DATA_DIR))
                {
                        mkdir (self :: DATA_DIR, 0777);
                }
                
                $this -> mOutput = new IrcOutput ();
                $this -> mInput  = new IrcInput ();
                
                $this -> mEngine = new UnoEngine ();
                $this -> mEngine -> setInput ($this -> mInput);
                $this -> mEngine -> setOutput ($this -> mOutput);
                
                $this -> loadSettings ();
        }
        
        /**
         * Opens a file in the data directory of this module and returns the
         * data contained within. If the file does not exist, creates it and
         * returns an empty array.
         * 
         * @param string $filename The filename (including extension) of the file.
         * @return array
         */
        
        public static function loadData ($filename)
        {
                if (!file_exists (self :: DATA_DIR . '/' . $filename))
                {
                        $data = array ();
                        file_put_contents (self :: DATA_DIR . '/' . $filename, serialize ($data));
                }
                else
                {
                        $data = unserialize (file_get_contents (self :: DATA_DIR . '/' . $filename));
                }
                
                return $data;
        }
        
        /**
         * Saves the given data to the given file. The filename should include
         * the extension.
         * 
         * @param string $filename The file to save to.
         * @param array $data The data to save.
         */
        
        public static function saveData ($filename, $data)
        {
                file_put_contents (self :: DATA_DIR . '/' . $filename, serialize ($data));
        }
        
        /**
         * Loads up the settings for this module and the Uno engine.
         */
        
        private function loadSettings ()
        {
                $settings = self :: loadData ('Settings.dat');
                
                $this -> mChannels = array ();
                if (isset ($settings ['Channels']))
                {
                        $this -> mChannels = $settings ['Channels'];
                }
        }
        
        /**
         * Saves the settings of the module and the Uno engine in their current
         * state.
         */
        
        private function saveSettings ()
        {
                $settings = self :: loadData ('Settings.dat');
                
                $settings ['Channels'] = $this -> mChannels;
                
                self :: saveData ('Settings.dat', $settings);
        }
        
        /**
         * This function will be called whenever a new module is being loaded.
         * If the Commands module is loaded, we register the commands in this
         * module.
         * 
         * @param ModuleBase $pModule The module that is being loaded.
         */
        
        public function onModuleLoad (ModuleBase $pModule)
        {
                if (get_class ($pModule) == 'Commands')
                {
                        $this -> mInput -> setCommandPrefix ($pModule ['Prefix']);
                        
                        $pModule -> registerCommand (new Command ('uno', array ($this, 'handleGlobalCommand'), 0));
                }
        }
        
        /**
         * 
         * 
         * @param Bot $pBot The bot which received the message
         * @param string $sChannel The channel this message was received in.
         * @param string $sNickname The nickname of the person speaking.
         * @param string $sMessage The message being said.
         */
        
        public function onChannelPrivmsg (Bot $pBot, $sChannel, $sNickname, $sMessage)
        {
                if (!isset ($this -> mChannels [strtolower ($sChannel)]))
                {
                        return;
                }
                
                $this -> mOutput -> setOutputInfo ($pBot, $sChannel);
                
                try
                {
                        $this -> mInput -> handleIrcCommand ($sChannel, $sNickname, $sMessage);
                }
                catch (Exception $ex)
                {
                        $this -> mOutput -> outputPublic ($ex -> getMessage ());
                }
                
                $this -> mOutput -> resetOutputInfo ();
        }
        
        /**
         * This method handles the global command, !uno, which knows a couple of
         * subcommands.
         * 
         * @param Bot $bot The bot which received the command.
         * @param string $sDestination The channel or nickname where the output should go.
         * @param string $channel The channel in which we received the command.
         * @param string $sNickname The person who activated the command.
         * @param array $params The parameters of the command, split by whitespace.
         * @param string $sMessage The complete string following the command trigger.
         */
        
        public function handleGlobalCommand (Bot $bot, $sDestination, $channel, $nickname, $params, $message)
        {
                static $levels = array
                (
                        'enable'        => ISecurityProvider :: BOT_OWNER,
                        'disable'       => ISecurityProvider :: BOT_OWNER,
                        'stats'         => 0,
                        'top'           => 0
                );
                
                $evalModule = ModuleManager :: getInstance () -> offsetGet ('Evaluation');
                if ($evalModule === false)
                {
                        echo 'Evaluation module not found. Administrator commands will not be available.';
                }
                
                if (!isset ($params [0]))
                {
                        self :: usage ($bot, $channel, $this -> mInput -> getCommandPrefix () . 'uno ' . implode ('|', array_keys ($levels)));
                        return;
                }
                
                if (isset ($levels [$params [0]]))
                {
                        if ($evalModule === false && $levels [$params [0]] > 0)
                        {
                                return;
                        }
                        else if ($evalModule !== false && $levels [$params [0]] > 0
                             && !$evalModule -> checkSecurity ($bot, $levels [$params [0]]))
                        {
                                return;
                        }
                        
                        // All clear to proceed.
                }
                
                switch ($params [0])
                {
                        case 'enable':
                        {
                                if (!isset ($this -> mChannels [strtolower ($channel)]))
                                {
                                        $this -> mChannels [strtolower ($channel)] = $channel;
                                        $this -> saveSettings ();
                                        
                                        self :: success ($bot, $channel, 'Enabled Uno in channel ' . $channel . '.');
                                }
                                else
                                {
                                        self :: error ($bot, $channel, 'Uno is already enabled in channel ' . $channel . '.');
                                }
                                
                                break;
                        }
                        
                        case 'disable':
                        {
                                if (isset ($this -> mChannels [strtolower ($channel)]))
                                {
                                        unset ($this -> mChannels [strtolower ($channel)]);
                                        $this -> saveSettings ();
                                        
                                        self :: success ($bot, $channel, 'Disabled Uno in channel ' . $channel . '.');
                                }
                                else
                                {
                                        self :: error ($bot, $channel, 'Uno is not enabled in channel ' . $channel . '.');
                                }
                                
                                break;
                        }
                        
                        case 'stats':
                        {
                                if (!isset ($this -> mChannels [strtolower ($channel)]))
                                {
                                        return;
                                }
                                
                                $stats = self :: loadData ('Stats.dat');
                                
                                if (!isset ($stats [strtolower ($channel)]))
                                {
                                        self :: ircPrivmsg ($bot, $channel, 'No statistics yet.');
                                        return;
                                }
                                
                                $stats = $stats [strtolower ($channel)];
                                
                                self :: ircPrivmsg ($bot, $channel, sprintf (
                                        'Uno Statistics | Longest game: %s | Shortest game: %s | Total time played: %s | '
                                        . 'Highest score: %s | Lowest score: %s | Total scored: %s',
                                        Util :: formatTime ($stats ['Time'] ['Longest'], true),
                                        Util :: formatTime ($stats ['Time'] ['Shortest'], true),
                                        Util :: formatTime ($stats ['Time'] ['Total'], true),
                                        number_format ($stats ['Score'] ['Highest']),
                                        number_format ($stats ['Score'] ['Lowest']),
                                        number_format ($stats ['Score'] ['Total'])
                                ));
                                
                                break;
                        }
                        
                        case 'top':
                        {
                                if (!isset ($this -> mChannels [strtolower ($channel)]))
                                {
                                        return;
                                }
                                
                                $scores = self :: loadData ('Scores.dat');
                                
                                if (!isset ($scores [strtolower ($channel)]))
                                {
                                        self :: ircPrivmsg ($bot, $channel, 'No scores yet for this channel.');
                                        return;
                                }
                                
                                $scores = $scores [strtolower ($channel)];
                                $playerScore = array ();
                                $playerWins = array ();
                                $playerLosses = array ();
                                
                                foreach ($scores as $player => $data)
                                {
                                        $playerScore [$player]  = $data ['Score'];
                                        $playerWins [$player]   = $data ['Wins'];
                                        $playerLosses [$player] = $data ['Losses'];
                                }
                                
                                array_multisort ($playerScore, SORT_DESC, $playerWins, SORT_DESC, $playerLosses, SORT_ASC, $scores);
                                unset ($playerScore, $playerWins, $playerLosses);
                                
                                $scores = array_slice ($scores, 0, 5);
                                
                                $output = 'Uno High Scores';
                                foreach ($scores as $player => $data)
                                {
                                        $output .= ' | ' . $player . ': ';
                                        $output .= $data ['Score'];
                                        $output .= ' (W:' . $data ['Wins'];
                                        $output .= ' L:' . $data ['Losses'];
                                        $output .= ')';
                                }
                                
                                self :: ircPrivmsg ($bot, $channel, $output);
                                
                                break;
                        }
                }
        }
        
        /**
         * Sends a message to a destination, this can be a channel or a nickname.
         * 
         * @param Bot $pBot The bot to send it with.
         * @param string $sDestination The destination.
         * @param string $sMessage The actual message to send.
         */
        
        public static function ircPrivmsg (Bot $pBot, $sDestination, $sMessage)
        {
                if ($pBot == null)
                {
                        throw new Exception ('No valid Bot object given.');
                }
                
                foreach (explode (PHP_EOL, $sMessage) as $sMessage)
                {
                        $sMessage = trim ($sMessage);
                        $pBot -> send ('PRIVMSG ' . $sDestination . ' :' . $sMessage);
                }
                
                return true;
        }
        
        /**
         * Sends a notice to a destination, this can be a channel or a nickname.
         * 
         * @param Bot $pBot The bot to send it with.
         * @param string $sDestination The destination.
         * @param string $sMessage The actual message to send.
         */
        
        public static function ircNotice (Bot $pBot, $sDestination, $sMessage)
        {
                if ($pBot == null)
                {
                        throw new Exception ('No valid Bot object given.');
                }
                
                foreach (explode (PHP_EOL, $sMessage) as $sMessage)
                {
                        $sMessage = trim ($sMessage);
                        $pBot -> send ('NOTICE ' . $sDestination . ' :' . $sMessage);
                }
                
                return true;
        }
        
        /**
         * Sends an error message to the destination. The message is prefixed
         * with '* Error:' in red, to distinguish the message from messages
         * which provide generally better news for the users.
         * 
         * @param Bot $pBot The bot to send it with.
         * @param string $sDestination The destination.
         * @param string $sMessage The actual message to send.
         */
        
        public static function error (Bot $pBot, $sDestination, $sMessage)
        {
                return self :: ircPrivmsg ($pBot, $sDestination, '4* Error: ' . $sMessage);
        }
        
        /**
         * Sends a general informational message to the user. The '* Info'
         * prefix message has a soft blue-ish color to indicate that's not a big
         * deal, unlike error messages.
         * 
         * @param Bot $pBot The bot to send it with.
         * @param string $sDestination The destination.
         * @param string $sMessage The actual message to send.
         */
        
        public static function info (Bot $pBot, $sDestination, $sMessage)
        {
                return self :: ircPrivmsg ($pBot, $sDestination, '10* Info: ' . $sMessage);
        }
        
        /**
         * Sends a message that requires the attention of the user, but is not
         * critical, unlike error messages. The '* Notice' prefix message has an
         * orange color to indicate that attention is required, but everything
         * will continue to work.
         * 
         * @param Bot $pBot The bot to send it with.
         * @param string $sDestination The destination.
         * @param string $sMessage The actual message to send.
         */
        
        public static function notice (Bot $pBot, $sDestination, $sMessage)
        {
                return self :: ircPrivmsg ($pBot, $sDestination, '7* Notice: ' . $sMessage);
        }
        
        /**
         * Sends a message to the user indicating that something worked out
         * nicely. The '* Success' prefix message has a green color to indicate
         * that all's good.
         * 
         * @param Bot $pBot The bot to send it with.
         * @param string $sDestination The destination.
         * @param string $sMessage The actual message to send.
         */
        
        public static function success (Bot $pBot, $sDestination, $sMessage)
        {
                return self :: ircPrivmsg ($pBot, $sDestination, '3* Success: ' . $sMessage);
        }
        
        /**
         * Sends an informational message to the user about how to use a certain
         * command. The '* Usage' prefix message has a soft blue-ish color to
         * indicate that's not a big deal, unlike error messages.
         * 
         * @param Bot $pBot The bot to send it with.
         * @param string $sDestination The destination.
         * @param string $sMessage The actual message to send.
         */
        
        public static function usage (Bot $pBot, $sDestination, $sMessage)
        {
                return self :: ircPrivmsg ($pBot, $sDestination, '10* Usage: ' . $sMessage);
        }
}